package spm.utils;

import java.text.MessageFormat;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * The class provides functionality to read/write the hibernate configuration
 * file the hibernate.properties. Be sure, that the file is in the classpath!
 * The setters methods write the new value to the file immediately.
 * </p>
 * <p>
 * This class is used during the setup/update process. User may set the specific
 * database parameters: database host name, port number, database name, user
 * name and password. Also, the user may set the type of the database (dialect).
 * Depending on the dialect, some properties will be set up automatically (e.g.
 * connection url and JDBC driver full name).
 * </p>
 * <p>
 * The typical usage of the class is following:
 * </p>
 * <p>
 * <code>
 * HibernateConfigurationManager hcm = HibernateConfigurationManager.getInstance();<br>
 * hcm.setDialect(dialect);<br>
 * hcm.setDatabaseHost(host);<br>
 * hcm.setDatabasePort(port);<br>
 * hcm.setDatabaseName(dbname);<br>
 * hcm.setConnectionURL(dialect);<br>
 * hcm.setConnectionUserName(userName);<br>
 * hcm.setConnectionPassword(password);
 * </code>
 * </p>
 * 
 * <p>
 * Please note, that the order of the methods is important! Do not call
 * <code>setConnectionURL()</code> before the database host, port and database
 * name is set!
 * </p>
 * <p>
 * Also be aware, that setting configuration parameters should be done before
 * the hibernate <code>SessionFactory</code> is initialized.
 * </p>
 * 
 * 
 * @author Evgeny Ryzhkov
 * 
 * $Id: HibernateConfigurationManager.java 16 2006-12-04 21:25:30Z
 * evgeny.ryzhkov $
 */
public final class HibernateConfigurationManager {
	
	// TODO Replace exceptions to custom ones

	/**
	 * <p>
	 * The logger.
	 * </p>
	 */
	private static final Log log = LogFactory
			.getLog(HibernateConfigurationManager.class);

	/**
	 * <p>
	 * The instance of the class.
	 * </p>
	 */
	private static HibernateConfigurationManager instance = null;

	/**
	 * <p>
	 * The name of the hibernate configuration file to read write properties.
	 * </p>
	 */
	private final String HIBERNATE_CFG_NAME = "hibernate.properties";

	/**
	 * <p>
	 * The databae dialect property name.
	 * </p>
	 */
	private final String HIBERNATE_DIALECT = "hibernate.dialect";

	/**
	 * <p>
	 * The connection URL property name.
	 * </p>
	 */
	private final String HIBERNATE_CONNECTION_URL = "hibernate.connection.url";

	/**
	 * <p>
	 * The connection driver property name.
	 * </p>
	 */
	private final String HIBERNATE_CONNECTION_DRIVER = "hibernate.connection.driver_class";

	/**
	 * <p>
	 * The database username property name.
	 * </p>
	 */
	private final String HIBERNATE_CONNECTION_USERNAME = "hibernate.connection.username";

	/**
	 * <p>
	 * The database user password property name.
	 * </p>
	 */
	private final String HIBERNATE_CONNECTION_PASSWORD = "hibernate.connection.password";

	/**
	 * <p>
	 * The special property for custom value (database name).
	 * </p>
	 */
	private final String CUSTOM_DB_NAME = "custom.db.name";

	/**
	 * <p>
	 * The special property for custom value (database host).
	 * </p>
	 */
	private final String CUSTOM_DB_HOST = "custom.db.host";

	/**
	 * <p>
	 * The special property for custom value (database port).
	 * </p>
	 */
	private final String CUSTOM_DB_PORT = "custom.db.port";

	/**
	 * <p>
	 * The internal number for the supported PostgreSQL dialect.
	 * </p>
	 */
	public static final int POSTGRESQL_DIALECT = 0;

	/**
	 * <p>
	 * The internal number for the supported My SQL dialect.
	 * </p>
	 */
	public static final int MYSQL_DIALECT = 1;

	/**
	 * <p>
	 * The internal number for the supported Firebird dialect.
	 * </p>
	 */
	public static final int FIREBIRD_DIALECT = 2;

	/**
	 * <p>
	 * The internal number for the supported Oracle dialect.
	 * </p>
	 */
	public static final int ORACLE_DIALECT = 3;

	/**
	 * <p>
	 * The internal number for the supported MS SQL dialect.
	 * </p>
	 */
	public static final int MSSQL_DIALECT = 4;

	/**
	 * <p>
	 * Array the all supported database dialects (hibernate specific values).
	 * </p>
	 */
	private final String[] supportedDialects = {
			"org.hibernate.dialect.PostgreSQLDialect",
			"org.hibernate.dialect.MySQLInnoDBDialect",
			"org.hibernate.dialect.FirebirdDialect",
			"org.hibernate.dialect.OracleDialect",
			"org.hibernate.dialect.SQLServerDialect" };

	/**
	 * <p>
	 * Array with full quialified JDBC driver names for the supported database
	 * dialects.
	 * </p>
	 */
	private final String[] jdbcDriverNames = { "org.postgresql.Driver",
			"com.mysql.jdbc.Driver", "org.firebirdsql.jdbc.FBDriver",
			"oracle.jdbc.driver.OracleDriver",
			"com.microsoft.jdbc.sqlserver.SQLServerDriver" };

	/**
	 * <p>
	 * Array with connection URLs templates for the supported database dialects.
	 * </p>
	 */
	private final String[] connectionURLs = { "jdbc:postgresql://{0}:{1}/{2}",
			"jdbc:mysql://{0}:{1}/{2}", "jdbc:firebirdsql://{0}:{1}/{2}",
			"jdbc:oracle:thin:@{0}:{1}:{2}",
			"jdbc:microsoft:sqlserver://{0}:{1};databaseName={2}" };

	/**
	 * <p>
	 * Array with default database ports.
	 * </p>
	 */
	private final String[] defaultPorts = { "5740", "3306", "3050", "1521",
			"1433" };

	private PropertiesConfiguration hibernateConfig = null;

	/**
	 * <p>
	 * Constructor.
	 * </p>
	 */
	private HibernateConfigurationManager() throws ConfigurationException {
		try {
			hibernateConfig = new PropertiesConfiguration();
			hibernateConfig.setFileName(HIBERNATE_CFG_NAME);
			hibernateConfig.load();
			log.debug("The hibernate.properties is load from the path "
					+ hibernateConfig.getPath());
		} catch (ConfigurationException cex) {
			log.error(cex.getMessage());
			throw new ConfigurationException(cex.getMessage());
		}

	}

	/**
	 * <p>
	 * Returns the instance of the HibernateConfigurationManager.
	 * </p>
	 * 
	 * @return the instance of the HibernateConfigurationManager
	 */
	public synchronized static HibernateConfigurationManager getInstance()
			throws ConfigurationException {
		if (instance == null) {
			instance = new HibernateConfigurationManager();
			log.debug("The instance is NULL - new instance is created.");
		}
		log.debug("The HibernateConfigurationManager instance is returned.");
		return instance;
	}

	/**
	 * <p>
	 * Disposes the instance.
	 * </p>
	 */
	public static void disposeInstance() {
		instance = null;
		log.debug("The HibernateConfigurationManager instance is disposed.");
	}

	/**
	 * <p>
	 * Returns the name of the database dialect (hibernate dialect).
	 * </p>
	 * 
	 * @return the name of the database dialect
	 */
	public String getDialect() {
		return hibernateConfig.getString(HIBERNATE_DIALECT);
	}

	/**
	 * <p>
	 * Sets the database dialect in the <code>hibernate.properties</code>.
	 * </p>
	 * 
	 * @param dialectIndex
	 *            the index of the database dialect
	 * @throws Exception
	 *             if the index is wrong (OutOfBounds)
	 */
	public synchronized void setDialect(int dialectIndex) throws Exception {
		log.debug("The dialectIndex is " + dialectIndex);
		hibernateConfig.setProperty(HIBERNATE_DIALECT,
				supportedDialects[dialectIndex]);
		hibernateConfig.setProperty(HIBERNATE_CONNECTION_DRIVER,
				jdbcDriverNames[dialectIndex]);
		hibernateConfig.save();
	}

	public String getConnectionDriver() {
		return hibernateConfig.getString(HIBERNATE_CONNECTION_DRIVER);
	}

	public String getConnectionUserName() {
		return hibernateConfig.getString(HIBERNATE_CONNECTION_USERNAME);
	}

	public synchronized void setConnectionUserName(String userName)
			throws ConfigurationException {
		log.debug("The userName is " + userName);
		hibernateConfig.setProperty(HIBERNATE_CONNECTION_USERNAME, userName);
		hibernateConfig.save();
	}

	public String getConnectionPassword() {
		return hibernateConfig.getString(HIBERNATE_CONNECTION_PASSWORD);
	}

	public synchronized void setConnectionPassword(String password)
			throws ConfigurationException {
		hibernateConfig.setProperty(HIBERNATE_CONNECTION_PASSWORD, password);
		hibernateConfig.save();
	}

	public String getDatabaseName() {
		return hibernateConfig.getString(CUSTOM_DB_NAME);
	}

	public synchronized void setDatabaseName(String databaseName)
			throws ConfigurationException {
		log.debug("The databaseName is " + databaseName);
		hibernateConfig.setProperty(CUSTOM_DB_NAME, databaseName);
		hibernateConfig.save();
	}

	public String getDatabaseHost() {
		return hibernateConfig.getString(CUSTOM_DB_HOST);
	}

	public synchronized void setDatabaseHost(String databaseHost)
			throws ConfigurationException {
		log.debug("The databaseHost is " + databaseHost);
		hibernateConfig.setProperty(CUSTOM_DB_HOST, databaseHost);
		hibernateConfig.save();
	}

	public String getDatabasePort() {
		return hibernateConfig.getString(CUSTOM_DB_PORT);
	}

	public synchronized void setDatabasePort(String databasePort)
			throws ConfigurationException {
		log.debug("The databasePort is " + databasePort);
		hibernateConfig.setProperty(CUSTOM_DB_PORT, databasePort);
		hibernateConfig.save();
	}

	public String getConnectionURL() {
		return hibernateConfig.getString(HIBERNATE_CONNECTION_URL);
	}

	/**
	 * Constructs the correct connection URL and set the corresponding property.
	 * 
	 * @param dialectIndex
	 *            the db dialect index
	 * @throws Exception
	 *             if the db dialect index is wrong
	 */
	public synchronized void setConnectionURL(int dialectIndex)
			throws Exception {
		String connectionURL = null;
		String connectionURLTemplate = connectionURLs[dialectIndex];
		String databaseHost = getDatabaseHost();
		String databasePort = getDatabasePort();
		String databaseName = getDatabaseName();

		log.debug("The DB dialect supportedDialects[" + dialectIndex + "] = '"
				+ supportedDialects[dialectIndex] + "'");
		log.debug("The connection URL template is " + connectionURLTemplate);
		log.debug("The databaseHost is " + databaseHost);
		log.debug("The databasePort is " + databasePort);
		log.debug("The databaseName is " + databaseName);

		if (databasePort.trim().equals("")) {
			databasePort = defaultPorts[dialectIndex];
			log.debug("The databasePort was empty. The default value is set - "
					+ databasePort);
		}

		connectionURL = MessageFormat.format(connectionURLTemplate,
				databaseHost, databasePort, databaseName);

		log.debug("The formatted connection URL is " + connectionURL);

		hibernateConfig.setProperty(HIBERNATE_CONNECTION_URL, connectionURL);
		hibernateConfig.save();
	}
}
